/*!
\copyright  Copyright (c) 2020-2025 Qualcomm Technologies International, Ltd.
            All Rights Reserved.
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\file
\ingroup    device_sync
\brief      Main component responsible for device data synchronisation.

Only one client is supported at the moment.

*/

#include "device_sync.h"
#include "device_sync_marshal_desc.h"

#include <device_properties.h>
#include <device_db_serialiser.h>
#include <bt_device.h>
#include <peer_signalling.h>
#include <key_sync.h>
#include <app_handover_if.h>

#include <device_list.h>
#include <task_list.h>
#include <logging.h>

#include <panic.h>

#include <stdlib.h>

LOGGING_PRESERVE_MESSAGE_TYPE(device_sync_messages_t)
ASSERT_MESSAGE_GROUP_NOT_OVERFLOWED(DEVICE_SYNC, DEVICE_SYNC_MESSAGE_END)


typedef enum
{
    DEVICE_SYNC_INTERNAL_PERSIST_DATA = 0,
    DEVICE_SYNC_INTERNAL_SYNC_PROPERTY_FOR_DEVICE,
    DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_DEVICE,
    DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_ALL_DEVICES,
} device_sync_internal_messages_t;

typedef struct
{
    device_t device;
    device_property_t property;
} DEVICE_SYNC_INTERNAL_SYNC_PROPERTY_FOR_DEVICE_T;

typedef struct
{
    device_t device;
} DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_DEVICE_T;

#define DATA_PERSIST_DELAY_MS 500

typedef struct
{
    const device_sync_callback_t *fn;
    device_sync_client_id_t client_id;
} device_sync_client_t;

typedef struct
{
    TaskData task;
    device_sync_client_t clients;
    task_list_t *listeners;

    /*! Iterator used to traverse the device list */
    device_list_iterator_t *iterator;

    /*! Number of outstanding TX CFMs for a given device.
     *  A TX CFM is expected for each device property that is synced. */
    uint16 num_pending_tx_cfms;

    /* Fields used for identifying duplicate sync requests in the message queue:
     * Message ID, device reference and property */
    MessageId matching_mid;
    device_t matching_device;
    device_property_t matching_property;
} device_sync_t;

static device_sync_t device_sync;

#define DeviceSyncGetTaskData()             (&device_sync)
#define DeviceSyncGetTask()                 (&device_sync.task)
#define DeviceSyncGetNumPendingTxCfms()     (device_sync.num_pending_tx_cfms)

static const earbud_device_property_t properties_to_sync[] = {
        device_property_supported_profiles,
        /* Need to sync upgrade_transport_connected property so secondary earbud will know 
           which handset is doing UPGRADE. so it can set the MRU flag for upgrade handset 
           before DFU reboot.
           After reboot, any earbud (old secondary can become PRIMARY) can become primary.
           if property is not synced then after reboot, when old SECONDARY became PRIMARY,
           before reboot secondary EB would have not SET the correct device with MRU flag. */
        device_property_upgrade_transport_connected,
        device_property_mru,
        device_property_battery_server_config_l,
        device_property_battery_server_config_r,
        device_property_gatt_server_config,
        device_property_media_control_client,
        device_property_call_control_client,
        device_property_audio_status,
        device_property_disconnected_for_sass_barge_in,
        device_property_tmap_client,
        device_property_handset_account_key_index,
        device_property_handset_bredr_context,
        device_property_handset_ble_context,
        device_property_gatt_service_discovery,
        device_property_le_audio_micp_config,
        device_property_le_audio_unicast_config,
        device_property_le_audio_volume_config,
        device_property_le_audio_pacs_config,
        device_property_le_audio_broadcast_config,
        device_property_qss_server_config,
        device_property_le_audio_source,
        device_property_le_voice_source,
        device_property_audio_volume,
        device_property_voice_volume,
        device_property_le_volume,
        device_property_flags, /* flags are synched but the receiver takes only the QHS enabled bit */
#ifdef INCLUDE_GBSS
        device_property_generic_broadcast_scan_config,
#endif
#ifdef INCLUDE_ACCESSORY_DEVICE_SERVICE
        device_property_accessory_type,
#endif /* INCLUDE_ACCESSORY_DEVICE_SERVICE */
};


static const earbud_device_property_t self_properties_to_sync[] = {
        device_property_v1_handset_service_config,
        device_property_va_flags,
        device_property_va_locale,
        device_property_ui_user_gesture,
        device_property_supported_profiles,
        device_property_sass_switch_disabled,
        device_property_handset_service_config,
        device_property_google_finder,
        device_property_google_finder_location_tracking_disabled,
        device_property_le_volume
};

static bool deviceSync_Veto(void)
{
    if(MessageCancelAll((Task)&device_sync.task, DEVICE_SYNC_INTERNAL_PERSIST_DATA))
    {
        DEBUG_LOG_INFO("deviceSync_Veto Persist data to not hold handover for too long");
        DeviceDbSerialiser_Serialise();
    }

    if (MessagesPendingForTask((Task)&device_sync.task, NULL))
    {
        DEBUG_LOG_INFO("deviceSync_Veto, Messages pending for Data sync task");
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

static void deviceSync_Commit(bool is_primary)
{
    UNUSED(is_primary);
}

REGISTER_HANDOVER_INTERFACE_NO_MARSHALLING(DEVICE_SYNC, deviceSync_Veto, deviceSync_Commit);

static void deviceSync_SendPropertyUpdateInd(device_t device, device_property_t property_id)
{
    MESSAGE_MAKE(msg, DEVICE_SYNC_PROPERTY_UPDATE_IND_T);
    msg->device = device;
    msg->property_id = property_id;

    TaskList_MessageSendWithSize(device_sync.listeners, DEVICE_SYNC_PROPERTY_UPDATE_IND, msg, sizeof(DEVICE_SYNC_PROPERTY_UPDATE_IND_T));
}

/* this function sets the local_flags but sets the DEVICE_FLAGS_QHS_CONNECTED bit if this is set in the remote_flags */
static void deviceSync_SetMergedFlags(device_t device, uint16 local_flags, uint16 remote_flags)
{
    uint16 merged_flags = local_flags;

    if (remote_flags & DEVICE_FLAGS_QHS_CONNECTED)
    {
        merged_flags |= DEVICE_FLAGS_QHS_CONNECTED;
    }

    Device_SetPropertyU16(device, device_property_flags, merged_flags);
}

static void deviceSync_PropertySyncRxInd(void *message)
{
    device_property_sync_t *msg = (device_property_sync_t *)message;
    device_t device = BtDevice_GetDeviceForBdAddr(&msg->addr);
    if(device)
    {
        deviceType device_type = BtDevice_GetDeviceType(device);

        if(device_type == DEVICE_TYPE_HANDSET || device_type == DEVICE_TYPE_ACCESSORY)
        {
            if(msg->id == device_property_mru)
            {
               /* MRU is a special case. It only matters when it is set to TRUE.
                  Properties are also not set directly but using BtDevice API.
               */
               if(msg->size == 1 && msg->data[0] == TRUE)
               {
                   appDeviceUpdateMruDevice(&msg->addr);
               }
            }
            else if(msg->id == device_property_flags)
            {
                if(msg->size == 2)
                {
                    uint16 local_flags = 0;
                    uint16 remote_flags = ((uint16)msg->data[0]) + (((uint16)msg->data[1])<<8);
                    Device_GetPropertyU16(device, device_property_flags, &local_flags);
                    DEBUG_LOG_INFO("deviceSync_PropertySyncRxInd (%p) flags local %x, remote %x", device, local_flags, remote_flags);
                    deviceSync_SetMergedFlags(device, local_flags, remote_flags);
                }
            }
            else
            {
                Device_SetProperty(device, msg->id, (void *)msg->data, msg->size);
            }


            MessageCancelAll((Task)&device_sync.task, DEVICE_SYNC_INTERNAL_PERSIST_DATA);
            MessageSendLater((Task)&device_sync.task, DEVICE_SYNC_INTERNAL_PERSIST_DATA, NULL, DATA_PERSIST_DELAY_MS);
        }
        else if(device_type == DEVICE_TYPE_EARBUD)
        {
            DEBUG_LOG_ALWAYS("deviceSync_PropertySyncRxInd device type enum:deviceType:%d, property enum:earbud_device_property_t:%d msg data 0x%x, msg size %d",
                    device_type, msg->id, msg->data[0], msg->size);

            PanicFalse(msg->id != device_property_flags);
            
            device_t self_device = BtDevice_GetSelfDevice();
            if(self_device)
            {
                Device_SetProperty(self_device, msg->id, (void *)msg->data, msg->size);
                deviceSync_SendPropertyUpdateInd(self_device, msg->id);
            }

            MessageCancelAll((Task)&device_sync.task, DEVICE_SYNC_INTERNAL_PERSIST_DATA);
            MessageSendLater((Task)&device_sync.task, DEVICE_SYNC_INTERNAL_PERSIST_DATA, NULL, DATA_PERSIST_DELAY_MS);
        }
    }
}

static void deviceSync_SendPropertySyncMessage(device_t device, device_property_t id, const uint8 *value, size_t size)
{
    device_property_sync_t *sync_msg = PanicUnlessMalloc(sizeof(device_property_sync_t) + sizeof(uint8) * size);

    sync_msg->addr = DeviceProperties_GetBdAddr(device);
    sync_msg->client_id = device_sync_client_core;
    sync_msg->id = id;
    sync_msg->size = size;
    memcpy(sync_msg->data, value, size);

    DeviceSync_SyncData(sync_msg);
}

static inline void deviceSync_InitMessageMatchingFields(MessageId id, device_t device, device_property_t property)
{
    DeviceSyncGetTaskData()->matching_mid = id;
    DeviceSyncGetTaskData()->matching_device = device;
    DeviceSyncGetTaskData()->matching_property = property;
}

static bool deviceSync_MatchSyncRequests(Task task, MessageId id, Message message)
{
    UNUSED(task);
    
    if (id != DeviceSyncGetTaskData()->matching_mid)
    {
        return FALSE;
    }

    if (id == DEVICE_SYNC_INTERNAL_SYNC_PROPERTY_FOR_DEVICE)
    {
        const DEVICE_SYNC_INTERNAL_SYNC_PROPERTY_FOR_DEVICE_T *req = (const DEVICE_SYNC_INTERNAL_SYNC_PROPERTY_FOR_DEVICE_T*)message;

        if (req->device == DeviceSyncGetTaskData()->matching_device &&
            req->property == DeviceSyncGetTaskData()->matching_property)
        {
            return TRUE;
        }
    }
    else if (id == DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_DEVICE)
    {
        const DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_DEVICE_T *req = (const DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_DEVICE_T*)message;

        if (req->device == DeviceSyncGetTaskData()->matching_device)
        {
            return TRUE;
        }
    }
    else if (id == DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_ALL_DEVICES)
    {
        return TRUE;
    }

    return FALSE;
}

static bool deviceSync_SendAllPropertiesForAllDevices(void)
{
    if (BtDevice_IsMyAddressPrimary() && appPeerSigIsConnected())
    {
        deviceSync_InitMessageMatchingFields(DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_ALL_DEVICES, NULL, device_property_max_num);

        if (!MessagePendingMatch(DeviceSyncGetTask(), TRUE, deviceSync_MatchSyncRequests))
        {
        MessageSendConditionallyOnTask(DeviceSyncGetTask(), DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_ALL_DEVICES,
                                 NULL, (Task *)&DeviceSyncGetTaskData()->iterator);
            return TRUE;
    }
}

    return FALSE;
}

static bool deviceSync_SendAllPropertiesForDevice(device_t device)
{
    if (BtDevice_IsMyAddressPrimary() && appPeerSigIsConnected())
    {
        deviceSync_InitMessageMatchingFields(DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_DEVICE, device, device_property_max_num);

        if (!MessagePendingMatch(DeviceSyncGetTask(), TRUE, deviceSync_MatchSyncRequests))
    {
        MESSAGE_MAKE(msg, DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_DEVICE_T);
        msg->device = device;
        MessageSendConditionally(DeviceSyncGetTask(), DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_DEVICE,
                                 msg, &DeviceSyncGetNumPendingTxCfms());
            return TRUE;
    }
}

    return FALSE;
}

static bool deviceSync_SendPropertyForDevice(device_t device, device_property_t property)
{
    if (BtDevice_IsMyAddressPrimary() && appPeerSigIsConnected())
    {
        deviceSync_InitMessageMatchingFields(DEVICE_SYNC_INTERNAL_SYNC_PROPERTY_FOR_DEVICE, device, property);

        if (!MessagePendingMatch(DeviceSyncGetTask(), TRUE, deviceSync_MatchSyncRequests))
    {
        MESSAGE_MAKE(msg, DEVICE_SYNC_INTERNAL_SYNC_PROPERTY_FOR_DEVICE_T);
        msg->device = device;
        msg->property = property;
        MessageSendConditionally(DeviceSyncGetTask(), DEVICE_SYNC_INTERNAL_SYNC_PROPERTY_FOR_DEVICE,
                                 msg, &DeviceSyncGetNumPendingTxCfms());
            return TRUE;
        }
    }

    return FALSE;
}

static void deviceSync_HandleInternalSendAllPropertiesForDevice(const DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_DEVICE_T* msg)
{
    DEBUG_LOG_VERBOSE("deviceSync_HandleInternalSendAllPropertiesForDevice(%p)", msg->device);

    if (DeviceList_IsDeviceOnList(msg->device))
    {
        device_t device = msg->device;
        const earbud_device_property_t *properties = NULL;
        size_t count = 0;

        if (device == BtDevice_GetSelfDevice())
        {
            properties = self_properties_to_sync;
            count = ARRAY_DIM(self_properties_to_sync);
        }
        else
        {
            properties = properties_to_sync;
            count = ARRAY_DIM(properties_to_sync);
        }

        /* Send all sync-worthy properties for this device */
        for (uint8 i = 0; i < count; ++i)
        {
            uint8 *ptr = NULL;
            size_t property_size = 0;

            if (Device_GetProperty(device, properties[i], (void *)&ptr, &property_size))
            {
                deviceSync_SendPropertySyncMessage(device, properties[i], ptr, property_size);
            }
        }

        /* Tell clients to send any other relevant properties for this device */
        if (device_sync.clients.fn)
        {
            device_sync.clients.fn->SyncReqCallback(device);
        }
    }
    else
    {
        /* The device has likely been deleted very recently */
        DEBUG_LOG_ERROR("deviceSync_HandleInternalSendAllPropertiesForDevice(%p), device does not exist", msg->device);
    }

    /* Queue property sync for next handset in the list */
    if (DeviceSyncGetTaskData()->iterator)
    {
        device_t next_device = DeviceList_IteratorNextDevice(DeviceSyncGetTaskData()->iterator);

        while (next_device != NULL)
        {
            if ((BtDevice_GetDeviceType(next_device) == DEVICE_TYPE_HANDSET ||
                 BtDevice_GetDeviceType(next_device) == DEVICE_TYPE_ACCESSORY) &&
                deviceSync_SendAllPropertiesForDevice(next_device))
            {
                break;
            }

            next_device = DeviceList_IteratorNextDevice(DeviceSyncGetTaskData()->iterator);
        }

        if (next_device == NULL)
        {
            /* End of the device list has been reached. Reset the index and unlock the queue. */
            DEBUG_LOG_INFO("deviceSync_HandleInternalSendAllPropertiesForDevice, reached end of list");

            DeviceList_DestroyDeviceIterator(DeviceSyncGetTaskData()->iterator);
            DeviceSyncGetTaskData()->iterator = NULL;
        }
    }

}

static void deviceSync_HandleInternalSendPropertyForDevice(const DEVICE_SYNC_INTERNAL_SYNC_PROPERTY_FOR_DEVICE_T* msg)
{
    uint8 *ptr = NULL;
    size_t property_size = 0;

    if (DeviceList_IsDeviceOnList(msg->device) && Device_GetProperty(msg->device, msg->property, (void *)&ptr, &property_size))
    {
        deviceSync_SendPropertySyncMessage(msg->device, msg->property, ptr, property_size);
    }
}

static void deviceSync_HandleInternalSendAllPropertiesForAllDevices(void)
{
    DEBUG_LOG_VERBOSE("deviceSync_HandleInternalSendAllPropertiesForAllDevices");

    PanicNotZero(DeviceSyncGetTaskData()->iterator);
    DeviceSyncGetTaskData()->iterator = DeviceList_CreateDeviceIterator(device_list_iterator_order_mru);

    /* Queue property sync for first (MRU) handset */
    device_t device = DeviceList_IteratorNextDevice(DeviceSyncGetTaskData()->iterator);

    while (device != NULL)
    {
        if ((BtDevice_GetDeviceType(device) == DEVICE_TYPE_HANDSET ||
             BtDevice_GetDeviceType(device) == DEVICE_TYPE_ACCESSORY) &&
            deviceSync_SendAllPropertiesForDevice(device))
        {
            break;
        }

        device = DeviceList_IteratorNextDevice(DeviceSyncGetTaskData()->iterator);
    }

    if (device == NULL)
    {
        /* End of the device list has been reached. Reset the index and unlock the queue. */
        DEBUG_LOG_INFO("deviceSync_HandleInternalSendAllPropertiesForAllDevices, reached end of list");

        DeviceList_DestroyDeviceIterator(DeviceSyncGetTaskData()->iterator);
        DeviceSyncGetTaskData()->iterator = NULL;
    }

    /* Queue property sync for SELF device as well */
    deviceSync_SendAllPropertiesForDevice(BtDevice_GetSelfDevice());
}

static void deviceSync_HandlePeerSigConnected(void)
{
    if(BtDevice_IsMyAddressPrimary())
    {
        DEBUG_LOG_VERBOSE("deviceSync_HandlePeerSigConnected");

        deviceSync_SendAllPropertiesForAllDevices();
    }
}

static void deviceSync_HandlePeerSigDisconnected(void)
{
    if (BtDevice_IsMyAddressPrimary())
    {
        DEBUG_LOG_VERBOSE("deviceSync_HandlePeerSigDisconnected");

        DeviceSyncGetNumPendingTxCfms() = 0;
        MessageCancelAll(DeviceSyncGetTask(), DEVICE_SYNC_INTERNAL_SYNC_PROPERTY_FOR_DEVICE);
        MessageCancelAll(DeviceSyncGetTask(), DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_DEVICE);
        MessageCancelAll(DeviceSyncGetTask(), DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_ALL_DEVICES);

        if (DeviceSyncGetTaskData()->iterator)
        {
            DeviceList_DestroyDeviceIterator(DeviceSyncGetTaskData()->iterator);
            DeviceSyncGetTaskData()->iterator = NULL;
        }
    }
}

static bool deviceSync_IsPropertyOnSyncList(device_property_t id)
{
    uint8 i;

    for(i = 0; i < ARRAY_DIM(properties_to_sync); ++i)
    {
        if(id == properties_to_sync[i])
        {
            return TRUE;
        }
    }

    return FALSE;
}

static bool deviceSync_IsPropertyOnSelfSyncList(device_property_t id)
{
    uint8 i;

    for(i = 0; i < ARRAY_DIM(self_properties_to_sync); ++i)
    {
        if(id == self_properties_to_sync[i])
        {
            return TRUE;
        }
    }

    return FALSE;
}

static void deviceSync_PropertyChangedHandler(device_t device, device_property_t id, const void *value, size_t size)
{
    UNUSED(value);
    UNUSED(size);

    if(BtDevice_IsMyAddressPrimary())
    {
        DEBUG_LOG_VERBOSE("deviceSync_PropertyChangedHandler device 0x%x, enum:earbud_device_property_t:%d", device, id);
        if(deviceSync_IsPropertyOnSyncList(id))
        {
            if((BtDevice_GetDeviceType(device) == DEVICE_TYPE_HANDSET ||
                BtDevice_GetDeviceType(device) == DEVICE_TYPE_ACCESSORY) &&
                KeySync_IsDeviceInSync(device))
            {
                deviceSync_SendPropertyForDevice(device, id);
            }
        }

        if(deviceSync_IsPropertyOnSelfSyncList(id))
        {
            if(BtDevice_GetDeviceType(device) == DEVICE_TYPE_SELF)
            {
                deviceSync_SendPropertyForDevice(device, id);
            }
        }
    }
}

static void deviceSync_MessageHandler(Task task, MessageId id, Message message)
{
    UNUSED(task);

    switch(id)
    {
        /* Messages handled on receiving peer */
        case PEER_SIG_MARSHALLED_MSG_CHANNEL_RX_IND:
        {
            PEER_SIG_MARSHALLED_MSG_CHANNEL_RX_IND_T *ind = (PEER_SIG_MARSHALLED_MSG_CHANNEL_RX_IND_T *)message;
            if(ind->type == MARSHAL_TYPE_device_property_sync_t)
            {
                device_property_sync_t *msg = (device_property_sync_t *)ind->msg;
                if(msg->client_id == device_sync_client_core)
                {
                    deviceSync_PropertySyncRxInd(ind->msg);
                }
                else if(msg->client_id == device_sync_client_device_pskey)
                {
                    DEBUG_LOG_VERBOSE("deviceSync_MessageHandler MARSHAL_TYPE_device_pskey_sync_t");
                    if(device_sync.clients.fn)
                    {
                        if(device_sync.clients.fn->SyncRxIndCallback(ind->msg))
                        {
                            device_property_sync_cfm_t *cfm_msg = PanicUnlessMalloc(sizeof(device_property_sync_cfm_t));

                            cfm_msg->addr = msg->addr;
                            cfm_msg->client_id = msg->client_id;
                            cfm_msg->id = msg->id;

                            appPeerSigMarshalledMsgChannelTx(&device_sync.task,
                                            PEER_SIG_MSG_CHANNEL_DEVICE_SYNC,
                                            cfm_msg, MARSHAL_TYPE_device_property_sync_cfm_t);
                        }
                    }
                }
            }
            else if(ind->type == MARSHAL_TYPE_device_property_sync_cfm_t)
            {
                /* Message handled on peer sending updates */
                device_property_sync_cfm_t *msg = (device_property_sync_cfm_t *)ind->msg;
                if(device_sync.clients.fn)
                {
                    device_sync.clients.fn->SyncCfmCallback(BtDevice_GetDeviceForBdAddr(&msg->addr), msg->id);
                }
            }
            free(ind->msg);
        }
        break;

        case PEER_SIG_MARSHALLED_MSG_CHANNEL_TX_CFM:
        {
            PEER_SIG_MARSHALLED_MSG_CHANNEL_TX_CFM_T *cfm = (PEER_SIG_MARSHALLED_MSG_CHANNEL_TX_CFM_T *)message;
            if(cfm->type == MARSHAL_TYPE_device_property_sync_t)
            {
                if (appPeerSigIsConnected())
                {
                /* For every TX CFM received, there must have been a preceding SendPropertyMessage
                 * which incremented this counter. */
                PanicFalse(DeviceSyncGetNumPendingTxCfms() > 0);
                DeviceSyncGetNumPendingTxCfms()--;
            }

                DEBUG_LOG_VERBOSE("deviceSync_MessageHandler PEER_SIG_MARSHALLED_MSG_CHANNEL_TX_CFM, cfms(%u)", DeviceSyncGetNumPendingTxCfms());
            }
        }
        break;

        case DEVICE_SYNC_INTERNAL_PERSIST_DATA:
        {
            DEBUG_LOG_VERBOSE("deviceSync_MessageHandler DEVICE_SYNC_INTERNAL_PERSIST_DATA");
            DeviceDbSerialiser_Serialise();

        }
        break;

        case DEVICE_SYNC_INTERNAL_SYNC_PROPERTY_FOR_DEVICE:
        {
            const DEVICE_SYNC_INTERNAL_SYNC_PROPERTY_FOR_DEVICE_T *msg =
                    (DEVICE_SYNC_INTERNAL_SYNC_PROPERTY_FOR_DEVICE_T *)message;
            deviceSync_HandleInternalSendPropertyForDevice(msg);
        }
        break;

        case DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_DEVICE:
        {
            const DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_DEVICE_T *msg =
                    (DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_DEVICE_T *)message;
            deviceSync_HandleInternalSendAllPropertiesForDevice(msg);
        }
        break;

        case DEVICE_SYNC_INTERNAL_SYNC_ALL_PROPERTIES_FOR_ALL_DEVICES:
        {
            deviceSync_HandleInternalSendAllPropertiesForAllDevices();
        }
        break;

        /* Messages handled on peer sending updates */
        case PEER_SIG_CONNECTION_IND:
        {
            PEER_SIG_CONNECTION_IND_T *ind = (PEER_SIG_CONNECTION_IND_T *)message;
            if (ind->status == peerSigStatusConnected)
            {
                deviceSync_HandlePeerSigConnected();
            }
            else if (ind->status == peerSigStatusDisconnected || ind->status == peerSigStatusLinkLoss)
            {
                deviceSync_HandlePeerSigDisconnected();
            }
        }
        break;

        case KEY_SYNC_DEVICE_COMPLETE_IND:
        {
            KEY_SYNC_DEVICE_COMPLETE_IND_T *msg = (KEY_SYNC_DEVICE_COMPLETE_IND_T *)message;

            DEBUG_LOG_VERBOSE("deviceSync_MessageHandler KEY_SYNC_DEVICE_COMPLETE_IND, lap=%06x", msg->bd_addr.lap);
            device_t device = BtDevice_GetDeviceForBdAddr(&msg->bd_addr);

            /* Must be called before attempting to sync the device's properties */
            if (device_sync.clients.fn)
            {
                device_sync.clients.fn->DeviceAddedToPeerCallback(device);
            }

            deviceSync_SendAllPropertiesForDevice(device);
        }
        break;

        default:
            break;
    }
}

bool DeviceSync_Init(Task init_task)
{
    UNUSED(init_task);

    memset(&device_sync, 0, sizeof(device_sync));

    device_sync.task.handler = deviceSync_MessageHandler;

    device_sync.listeners = TaskList_CreateWithCapacity(1);

    appPeerSigClientRegister((Task)&device_sync.task);

    appPeerSigMarshalledMsgChannelTaskRegister(&device_sync.task,
            PEER_SIG_MSG_CHANNEL_DEVICE_SYNC,
            device_sync_marshal_type_descriptors,
            NUMBER_OF_MARSHAL_OBJECT_TYPES);

    KeySync_RegisterListener((Task)&device_sync.task);

    Device_RegisterOnPropertySetHandler(deviceSync_PropertyChangedHandler);

    return TRUE;
}

void DeviceSync_RegisterCallback(device_sync_client_id_t client_id, const device_sync_callback_t *callback)
{
    device_sync.clients.fn = callback;
    device_sync.clients.client_id = client_id;
}

void DeviceSync_RegisterForNotification(Task listener)
{
    TaskList_AddTask(device_sync.listeners, listener);
}

void DeviceSync_SyncData(void *msg)
{
    appPeerSigMarshalledMsgChannelTx(&device_sync.task,
                PEER_SIG_MSG_CHANNEL_DEVICE_SYNC,
                msg, MARSHAL_TYPE_device_property_sync_t);
    DeviceSyncGetNumPendingTxCfms()++;

    DEBUG_LOG_VERBOSE("DeviceSync_SyncData, cfms(%u)", DeviceSyncGetNumPendingTxCfms());
}
